using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;

using Atomic.Libraries;
using Atomic.Libraries.Mathematics;
using Atomic.Structures;

namespace Atomic.Interfaces.Atat
{
	/// <summary>
	/// A wrapper interface to the ATAT genstr tool.
	/// </summary>
	public static class AtatStructureGenerator
	{
		/// <summary>
		/// Generates all symmetrically independent combinations of structures with <see cref="PlaceholderAtom" /> being replaced by its containing atoms.
		/// </summary>
		public static IEnumerable<Structure> Generate(Structure lattice)
		{
			//lattice = new Structure(new Lattice(Basis.StandardBasis, lattice.Lattice.A.PhysicalCoordinates, lattice.Lattice.B.PhysicalCoordinates, lattice.Lattice.C.PhysicalCoordinates), lattice.Sites);
			//lattice = new Structure(Basis.StandardBasis, new Lattice(Basis.StandardBasis, lattice.Lattice.A.PhysicalCoordinates, lattice.Lattice.B.PhysicalCoordinates, lattice.Lattice.C.PhysicalCoordinates), lattice.Sites);
			//lattice.Show();

			using (TempFile temp = new TempFile())
			{
				lattice.Write(temp.File);

				// Start genstr process. IEEE 754 double-precision has up to 17 significant digits.
				Process p = new Process();
				p.StartInfo.RedirectStandardOutput = true;
				p.StartInfo.RedirectStandardError = true;
				p.StartInfo.UseShellExecute = false;
				p.StartInfo.FileName = "genstr";
				p.StartInfo.Arguments = string.Format("-n={0} -sig=17 -l={1}", lattice.Sites.Count, temp.File.FullName);

				p.Start();

				// The structures are written to standard output separated by lines containing "end".
				foreach (Structure s in Parse(p.StandardOutput))
				{
					yield return s;
				}

				p.WaitForExit();

				if (p.ExitCode != 0)
				{
					throw new Exception(string.Format("Failed running genstr: {0}", p.StandardError.ReadToEnd()));
				}
			}
		}

		/// <summary>
		/// Generates all symmetrically independent combinations of structures with <see cref="PlaceholderAtom" /> being replaced by its containing atoms.
		/// The generated structures are mapped back to the original lattice and site positions and in the original order.
		/// </summary>
		public static IEnumerable<Structure> GenerateMapped(Structure lattice)
		{
			foreach (Structure structure in Generate(lattice))
			{
				yield return MapStructure(lattice, structure);
			}
		}

		private static Structure MapStructure(Structure lattice, Structure structure)
		{
			double tol = 1.0e-8;

			List<Site> sites = new List<Site>();
			foreach (Site latticeSite in lattice.Sites)
			{
				foreach (Site structureSite in structure.Sites)
				{
					if (structureSite.Atom == latticeSite.Atom || (latticeSite.Atom is PlaceholderAtom) && ((PlaceholderAtom)latticeSite.Atom).Atoms.Contains(structureSite.Atom))
					{
						// Decompose in coordinates relative to the lattice of the output structure.
						SpaceVector d = structure.Lattice.Decompose(latticeSite.Position).Coordinates - structure.Lattice.Decompose(structureSite.Position).Coordinates;

						// If this is the same site, d has to be integers only. Check the rounded off values in the physical coordinates.
						if (SpaceVector.Norm(structure.Lattice.PhysicalMap * new SpaceVector(d[0] - Math.Round(d[0]), d[1] - Math.Round(d[1]), d[2] - Math.Round(d[2]))) < tol)
						{
							// Use original representation of the position.
							sites.Add(new Site(structureSite.Atom, latticeSite.Position));
							break;
						}
					}
				}
			}

			if (sites.Count != structure.Sites.Count)
			{
				throw new Exception("Couldn't map output structure.");
			}

			return new Structure(lattice.Basis, lattice.Lattice, sites);
		}

		/// <summary>
		/// Parse output of ATATs genstr.
		/// </summary>
		public static IEnumerable<Structure> Parse(FileInfo file)
		{
			using (StreamReader reader = new StreamReader(file.FullName))
			{
				return Parse(reader);
			}
		}

		/// <summary>
		/// Parse output of ATATs genstr.
		/// </summary>
		public static IEnumerable<Structure> Parse(TextReader reader)
		{
			string s;
			StringBuilder sb = new StringBuilder(); 
			while ((s = reader.ReadLine()) != null)
			{
				if (s == "end")
				{
					yield return Structure.Read(new StringReader(sb.ToString()));
					sb.Clear();
				}
				else if (s != "")
				{
					sb.AppendLine(s);
				}
			}
		}
	}
}
